package com.jf.cloud.service.sysmanager.common.util;


import org.apache.log4j.Logger;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;


/**
 * ★★类名:com.igame.app.base.ReflectUtil <pre>
 * 描述: 获取对象字段 以及相应的值 （一般使用与调试）
 * 编写者:ouyang
 * 版权: Copyright (C) 2012  www.3yx.com
 * 创建时间: 2012-2-7
 * </pre>
 */
@SuppressWarnings("all")
public final class ReflectUtil {
	private static Logger log = Logger.getLogger(ReflectUtil.class);
	
	private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");

    /**
     * Check to see if two sets of param types are compatible with each other.
     *
     * @param typesReq The required param types.
     * @param types    The params in question.
     * @return true    If the types are compatible, false otherwise.
     */
    public static boolean typesCompatible(Class[] typesReq, Class[] types) {
        if (typesReq != null && types != null && typesReq.length != types.length) {
            throw new IllegalArgumentException("ReflectUtil.typesCompatible(): Types must be same length.");
        }

        if (typesReq == null && types == null) {
            return true;
        }

        boolean valid = true;
        int i = 0;

        while (i < typesReq.length && valid == true) {
            //Check to see if they are the same of subclass
            if (typesReq[i].isAssignableFrom(types[i])) {
                valid = true;
            } else {
                valid = false;
            }

            i++;
        }

        return valid;
    }

    /**
     * @param obj         The source object to reflect
     * @param superLevels The level of superClasses to reflect. For a non-inherited
     *                    class, this will be 0. For classes where you also want
     *                    to reflect the parent fields, set to 1 or higher.
     * @return string value representing the given object
     */
    private static Object[] p_fieldsToArray(Object obj, int superLevels) {
        List result = new ArrayList();
        try {
            Class sourceClass = obj.getClass();
            for (int level = 0; level <= superLevels; level++) {
                Field[] fields = sourceClass.getDeclaredFields();
                for (int i = 0; i < fields.length; i++) {
                    Field f = fields[i];
                    int modifier = f.getModifiers();
                    if (Modifier.isStatic(modifier)) {
                        continue;
                    }
                    f.setAccessible(true);
                    String name = f.getName();
                    if (name.indexOf("m_") == 0) {
                        name = name.substring(2);
                    }
                    Object value = f.get(obj);

                    result.add(name);
                    result.add(value);
                }
                sourceClass = sourceClass.getSuperclass();
            }
        }
        catch (Exception e) {
            log.error("ReflectUtil: Problem reflecting fields", e);
        }

        return result.toArray();
    }

    public static Object[] fieldsToArray(Object obj) {
        int levels = 0;
        Class sourceClass = obj.getClass();
        while (!sourceClass.getName().equals("java.lang.Object")) {
            levels++;
            sourceClass = sourceClass.getSuperclass();
        }
        levels--;

        return p_fieldsToArray(obj, levels);
    }

    /**
     * @param obj         The source object to reflect
     * @param superLevels The level of superClasses to reflect. For a non-inherited
     *                    class, this will be 0. For classes where you also want
     *                    to reflect the parent fields, set to 1 or higher.
     * @return string value representing the given object
     */
    private static String p_fieldsToString(Object obj, int superLevels) {
        String[] results = new String[superLevels + 1];
        StringBuffer result = new StringBuffer();
        try {
            Class sourceClass = obj.getClass();

            for (int level = 0; level <= superLevels; level++) {
                Field[] fields = sourceClass.getDeclaredFields();

                for (int i = 0; i < fields.length; i++) {
                    Field f = fields[i];
                    int modifier = f.getModifiers();
                    if (Modifier.isStatic(modifier)) {
                        continue;
                    }
                    f.setAccessible(true);
                    String name = f.getName();
                    if (name.indexOf("m_") == 0) {
                        name = name.substring(2);
                    }
                    Object tempObject = f.get(obj);
                    result.append(name);
                    result.append("[");
                    result.append(tempObject);
                    result.append("]");
                    if (i < fields.length - 1) {
                        result.append(",");
                    }
                    result.append(" ");
                }
                results[superLevels - level] = result.toString();
                result.setLength(0);
                sourceClass = sourceClass.getSuperclass();
            }

            for (int i = 0; i <= superLevels; i++) {
                result.append(results[i]);
            }
        }
        catch (Exception ex) {
            log.error("ReflectUtil: Problem reflecting fields", ex);
        }

        return result.toString();
    }


    public static String fieldsToString(Object obj) {
        int levels = 0;
        Class sourceClass = obj.getClass();
        while (!sourceClass.getName().equals("java.lang.Object")) {
            levels++;
            sourceClass = sourceClass.getSuperclass();
        }
        levels--;

        return p_fieldsToString(obj, levels);
    }

    public static String traceString(String loc, Object src) {
        Object[] state = fieldsToArray(src);

        StringBuffer buffer = new StringBuffer();
        buffer.append(src.getClass().getName().substring(src.getClass().getName().lastIndexOf('.') + 1));
        buffer.append('@');
        buffer.append(Integer.toHexString(src.hashCode()));
        buffer.append('.');
        buffer.append(loc);
        buffer.append('[');
        if (state != null) {
            int length = state.length;
            for (int i = 0; i < length; i++) {
                buffer.append(state[i]);
                if ((i % 2) == 0) {
                    buffer.append("[");
                } else {
                    buffer.append("]");
                }
            }
        }
        buffer.append(']');

        return buffer.toString();
    }

    /**
     * Thisd method checks the equality between the two objects recursively
     *
     * @param obj1 object1 to be compared
     * @param obj2 object1 to be compared
     * @return true/false
     */
    public static boolean areObjectsEqual(Object obj1, Object obj2) {
        if (obj1 == null && obj2 == null) {
            return true;
        }

        if ((obj1 != null && obj2 == null) || (obj1 == null && obj2 != null)) {
            return false;
        }

        if (!obj1.getClass().getName().equals(obj2.getClass().getName())) {
            return false;
        }

        int levels = 0;
        Class sourceClass = obj1.getClass();

        // Count all the super classes till java.lang.object
        while (!sourceClass.getName().equals("java.lang.Object")) {
            levels++;
            sourceClass = sourceClass.getSuperclass();
        }

        levels--;
        return p_equals(obj1, obj2, levels);
    }

    private static boolean p_equals(Object obj1, Object obj2, int superLevels) {
        try {
            Class sourceClass = obj1.getClass();

            // Check for equality for this class and all the super classes
            for (int level = 0; level <= superLevels; level++) {
                //get all the fields of the class
                Field[] fields = sourceClass.getDeclaredFields();

                //Check for equality for all fields
                for (int i = 0; i < fields.length; i++) {
                    Field f = fields[i];

                    //Ignore static fields (class level fields) as
                    //they fall outside the object equality condition
                    int modifier = f.getModifiers();
                    if (Modifier.isStatic(modifier)) {
                        continue;
                    }

                    //Setting the accessibility of the field to true.
                    //This is necessary for accessing private/protected fields
                    f.setAccessible(true);

                    //Get the name of the field
                    String name = f.getName();

                    //get the value of the field for both objects
                    Object tempObject1 = f.get(obj1);
                    Object tempObject2 = f.get(obj2);

                    //If the field is array then establish the equality
                    //of arrays by java.util.Arrays.equals(object[], Object [])
                    if ((tempObject1 != null) && tempObject1.getClass().isArray()) {
                        if (!Arrays.equals((Object[]) tempObject1, (Object[]) tempObject2)) {
                            return false;
                        }
                    } else {
                        //If the field is the intance of date, then compare it with
                        //pre-constructed date formatter (MM/dd/yyyy hh:mm:ss z )
                        if ((tempObject1 != null && tempObject2 != null) && tempObject1 instanceof Date) {
                            if (!format.format((Date) tempObject1).equals(format.format((Date) tempObject2))) {
                                return false;
                            }
                        } else {
                            //Establish the equality condition taking care of the situation
                            //when either of the value is null
                            if (tempObject1 != null && !tempObject1.equals(tempObject2)) {
                                return false;
                            }
                            if (tempObject2 != null && !tempObject2.equals(tempObject1)) {
                                return false;
                            }
                        }
                    }
                }

                sourceClass = sourceClass.getSuperclass();
            }
        }
        catch (Exception ex) {
        	log.error(ex.toString());
            return false;
        }
        return true;
    }
}
